
/*
命名规范

SK_体型名_套装名_部位名
例如：
SK_female_default_body

Max中需要填写体型名
【导出骨骼】 会将骨骼FBX输出到			根目录/体型名/SK_体型名.fbx（与体型名面片一同输出，添加SK_前缀）（如果有多个Root会报错停下）
【导出模型】 会将模型FBX输出到			根目录/体型名/体型名_套装名/蒙皮模型名.fbx（体型名_套装名是蒙皮模型的名字）（如果有多个Root会报错停下）
【导出动画】 会将骨骼和动画FBX输出到		根目录/体型名/common/max文件名.fbx（如果有多个Root会选中）
【批量动画】 会将骨骼和动画FBX输出到		根目录/体型名/common/max文件名.fbx（）
*如果场景中有多个角色，以上导出会报错并跳过此次导出（不会打断批处理）

【单个多角色动画】会将骨骼和动画FBX输出到       根目录/SKEL体型名/common/max文件名_ID名.fbx（如果#后为空则没有 _ID名)
【批量多角色动画】会将骨骼和动画FBX输出到       根目录/SKEL体型名/common/max文件名_ID名.fbx（如果#后为空则没有 _ID名)

*会依据多少个角色输出多个文件
并检查每个角色身上Root下的*skelid文字组件填写的内容（用#）
文件名依次为 max文件名_(*skelid 填写的内容)
如果有角色没有正确填写*skelid名就会报错并跳过此次导出（不会打断批处理）

【布料代理模型】
布料片命名需要加 #cloth 后缀
非隐藏的布料片会和和它同名的mesh一起输出
例如：
SK_female_default_body#cloth
SK_female_default_body#cloth_back    (多个布料部位可在 #cloth 后面自行添加名字)
SK_female_default_body#cloth_left    (多个布料部位可在 #cloth 后面自行添加名字)
*/

rollout Gujian4_ExportFbxUtil "FBX导出工具"
(
	-- values
	local config_fbxstring = "FBX Export Settings"
	local outputPath = ""
	local defaultFbxParams = A2U_FbxParams()

	fn adjustPath path_name = 
	(
		local ret_path = path_name
		local c = path_name.count
		-- print path_name[c]
		if (path_name[c] == "\\" or 
			path_name[c] == "/") then
			(
				c = c - 1
				ret_path = substring path_name 1 c
			)
		-- print ret_path
		return ret_path	
	)

	fn getIniFbxParam paramKey =
	(
		local v = getINISetting A2UConfigPath config_fbxstring paramKey
		if (undefined == v or "" == v) then
		(
			v = getProperty defaultFbxParams paramKey
			--setINISetting A2UConfigPath config_fbxstring paramKey v
		)
		return v
	)

	fn setFbxParams isAnim =
	(
		if isAnim then
		(
			fbxExporterSetParam "Animation" true
			fbxExporterSetParam "BakeAnimation" true
			fbxExporterSetParam "BakeframeStart" animationRange.start
			fbxExporterSetParam "BakeframeEnd" animationRange.end
			fbxExporterSetParam "BakeframeStep" 1
			fbxExporterSetParam "BakeResampleAnimation" true
			fbxExporterSetParam "AxisConversionMethod" "Animation" 		--?

			fbxExporterSetParam "Resampling" (execute(getIniFbxParam "Resampling"))
			fbxExporterSetParam "Skin" (execute(getIniFbxParam "Skin"))
			fbxExporterSetParam "Shape" (execute(getIniFbxParam "Shape"))
			fbxExporterSetParam "CAT2HIK" (execute(getIniFbxParam "CAT2HIK"))
		)
		else
		(
			fbxExporterSetParam "Animation" false
			fbxExporterSetParam "BakeAnimation" false
			fbxExporterSetParam "AxisConversionMethod" "None" 		--?
		)

		fbxExporterSetParam "FileVersion" (getIniFbxParam "FileVersion")
		fbxExporterSetParam "ASCII" (execute(getIniFbxParam "ASCII"))

		fbxExporterSetParam "SmoothingGroups" (execute(getIniFbxParam "SmoothingGroups"))
		fbxExporterSetParam "NormalsPerPoly" (execute(getIniFbxParam "NormalsPerPoly"))
		fbxExporterSetParam "TangentSpaceExport" (execute(getIniFbxParam "TangentSpaceExport"))
		fbxExporterSetParam "GeomAsBone" (execute(getIniFbxParam "GeomAsBone"))
		fbxExporterSetParam "Triangulate" (execute(getIniFbxParam "Triangulate"))
		fbxExporterSetParam "PreserveEdgeOrientation" (execute(getIniFbxParam "PreserveEdgeOrientation"))

		fbxExporterSetParam "ColladaTriangulate" false
		fbxExporterSetParam "ColladaSingleMatrix" false
		fbxExporterSetParam "ColladaFrameRate" 60.f

		fbxExporterSetParam "Cameras" (execute(getIniFbxParam "Cameras"))
		fbxExporterSetParam "Lights" (execute(getIniFbxParam "Lights"))

		fbxExporterSetParam "EmbedTextures" (execute(getIniFbxParam "EmbedTextures"))
		fbxExporterSetParam "UpAxis" (getIniFbxParam "UpAxis")

		fbxExporterSetParam "Convert2Tiff" false
		fbxExporterSetParam "ConvertUnit" "cm"
		fbxExporterSetParam "FilterKeyReducer" true
		fbxExporterSetParam "GenerateLog" false
		fbxExporterSetParam "PointCache" false
		fbxExporterSetParam "PreserveInstances" false
		fbxExporterSetParam "RemoveSingleKeys" true
		fbxExporterSetParam "ScaleFactor" 1.0
		fbxExporterSetParam "SelectionSetExport" false
		fbxExporterSetParam "ShowWarnings" true
		fbxExporterSetParam "SmoothMeshExport" false
		fbxExporterSetParam "UseSceneName" false
	)
	
	/********************************************************************/
	
	group "导出路径/参数"
	(
		-- 填写fbx输出文件夹路径，格式参考 D:\\MyFolder\\Export，依据自己情况设置
		label lbl "根目录" align:#left across:2
		editText edtOutputRoot "" align:#right width:100
		
		button btnEditFbxParams "修改FBX导出参数"
	)

	---------------------------
	
	rollout boxFbxParams "修改FBX导出参数"
	(
		group "File Version"
		(
			dropdownlist ddlFbxVersion "" items:#("FBX201600","FBX201800") width:85 across:2 offset:[-6,0] tooltip:"Choose which FBX version to output as"		--FileVersion
			checkbox chkFbxASCII "ASCII" checked:false align:#left offset:[20,3] tooltip:"Output the FBX file in ASCII format for easy reading, otherwise output as binary"		--ASCII
		)
		
		group "Geometry"
		(
			checkbox chkSmoothingGroups "Smoothing Groups" offset:[-6,0]		--SmoothingGroups
			checkbox chkNormalsPerPoly "Split Per-Vertex Normals" offset:[-6,0]		--NormalsPerPoly
			checkbox chkTangentSpaceExport "Tangent and Binormals" offset:[-6,0]		--TangentSpaceExport
			checkbox chkGeomAsBone "Convert Deforming Dummies to Bones" offset:[-6,0] tooltip:"Convert Deforming Dummies to Bones"		--GeomAsBone
			checkbox chkTriangulate "Triangulate" offset:[-6,0]		--Triangulate
			checkbox chkPreserveEdges "Preserver Edge Orientation" offset:[-6,0]		--PreserveEdgeOrientation
		)
		
		group "Animation"
		(
			spinner spnResampling "Resampling:" range:[0,120,60] type:#float fieldwidth:50 offset:[-108,0]		--Resampling
			checkbox chkSkin "Skins" offset:[-6,0]		--Skin
			checkbox chkShape "Morphs" offset:[-6,0]		--Shape
		)
		
		group "Misc"
		(
			checkbox chkCameras "Cameras" offset:[-6,0]		--Cameras
			checkbox chkLights "Lights" offset:[-6,0]		--Lights
			checkbox chkEmbedTextures "Embed Media" offset:[-6,0]		--EmbedTextures
			checkbox chkCAT2HIK "CAT Characters as HIK" offset:[-6,0]	--CAT2HIK
		)
		
		group "Axis Conversion"	-- Axis Conversion
		(
			label lblUpAxis "Up Axis" align:#left offset:[0,2] across:2 
			dropdownlist ddlUpAxis "" items:#("Y","Z") offset:[-25,-1] width:35		--UpAxis
		)

		button btnResetFbxParams "重置FBX导出参数"

		fn refreshFbxParams =
		(
			local v = getIniFbxParam "FileVersion"
			(
				for i = 1 to ddlFbxVersion.items.count do
				(
					if (findString ddlFbxVersion.items[i] v) == 1 then
					(
						ddlFbxVersion.selection = i
						exit
					)
				)
			)

			v = getIniFbxParam "ASCII"
			(
				chkFbxASCII.checked = v as booleanClass
			)

			---------------------------

			v = getIniFbxParam "SmoothingGroups"
			(
				chkSmoothingGroups.checked = v as booleanClass
			)

			v = getIniFbxParam "NormalsPerPoly"
			(
				chkNormalsPerPoly.checked = v as booleanClass
			)

			v = getIniFbxParam "TangentSpaceExport"
			(
				chkTangentSpaceExport.checked = v as booleanClass
			)

			v = getIniFbxParam "GeomAsBone"
			(
				chkGeomAsBone.checked = v as booleanClass
			)

			v = getIniFbxParam "Triangulate"
			(
				chkTriangulate.checked = v as booleanClass
			)

			v = getIniFbxParam "PreserveEdgeOrientation"
			(
				chkPreserveEdges.checked = v as booleanClass
			)

			---------------------------

			v = getIniFbxParam "Resampling"
			(
				spnResampling.value = v as float
			)

			v = getIniFbxParam "Skin"
			(
				chkSkin.checked = v as booleanClass
			)

			v = getIniFbxParam "Shape"
			(
				chkShape.checked = v as booleanClass
			)

			---------------------------

			v = getIniFbxParam "Cameras"
			(
				chkCameras.checked = v as booleanClass
			)

			v = getIniFbxParam "Lights"
			(
				chkLights.checked = v as booleanClass
			)

			v = getIniFbxParam "EmbedTextures"
			(
				chkEmbedTextures.checked = v as booleanClass
			)

			v = getIniFbxParam "CAT2HIK"
			(
				chkCAT2HIK.checked = v as booleanClass
			)

			---------------------------

			local v = getIniFbxParam "UpAxis"
			(
				for i = 1 to ddlUpAxis.items.count do
				(
					if (findString ddlUpAxis.items[i] v) == 1 then
					(
						ddlUpAxis.selection = i
						exit
					)
				)
			)
		)

		---------------------------
		
		on ddlFbxVersion selected val do
		(
			setIniSetting A2UConfigPath config_fbxstring "FileVersion" ddlFbxVersion.items[val]
		)
		
		on chkFbxASCII changed theState do
		(
			setIniSetting A2UConfigPath config_fbxstring "ASCII" (theState as string)
		)
		
		on chkSmoothingGroups changed theState do
		(
			setIniSetting A2UConfigPath config_fbxstring "SmoothingGroups" (theState as string)
		)
		
		on chkNormalsPerPoly changed theState do
		(
			setIniSetting A2UConfigPath config_fbxstring "NormalsPerPoly" (theState as string)
		)
		
		on chkTangentSpaceExport changed theState do
		(
			setIniSetting A2UConfigPath config_fbxstring "TangentSpaceExport" (theState as string)
		)
		
		on chkGeomAsBone changed theState do
		(
			setIniSetting A2UConfigPath config_fbxstring "GeomAsBone" (theState as string)
		)
		
		on chkTriangulate changed theState do
		(
			setIniSetting A2UConfigPath config_fbxstring "Triangulate" (theState as string)
		)
		
		on chkPreserveEdges changed theState do
		(
			setIniSetting A2UConfigPath config_fbxstring "PreserveEdgeOrientation" (theState as string)
		)

		on spnResampling changed value do
		(
			setIniSetting A2UConfigPath config_fbxstring "Resampling" (value as string)
		)
		
		on chkSkin changed theState do
		(
			setIniSetting A2UConfigPath config_fbxstring "Skin" (theState as string)
		)
		
		on chkShape changed theState do
		(
			setIniSetting A2UConfigPath config_fbxstring "Shape" (theState as string)
		)
		
		on chkCameras changed theState do
		(
			setIniSetting A2UConfigPath config_fbxstring "Cameras" (theState as string)
		)
		
		on chkLights changed theState do
		(
			setIniSetting A2UConfigPath config_fbxstring "Lights" (theState as string)
		)
		
		on chkEmbedTextures changed theState do
		(
			setIniSetting A2UConfigPath config_fbxstring "EmbedTextures" (theState as string)
		)
		
		on chkCAT2HIK changed theState do
		(
			setIniSetting A2UConfigPath config_fbxstring "CAT2HIK" (theState as string)
		)
			
		on ddlUpAxis selected val do
		(
			setIniSetting A2UConfigPath config_fbxstring "UpAxis" ddlUpAxis.items[val]
		)

		---------------------------
		
		on btnResetFbxParams pressed do
		(
			delIniSetting A2UConfigPath config_fbxstring
			refreshFbxParams()
		)

		on boxFbxParams open do
		(
			refreshFbxParams()
		)
	)
	
	---------------------------

	on edtOutputRoot entered text do
	(
		A2UFn_SetFBXExportPath_Gujian4(text)		
		outputPath = edtOutputRoot.text
	)

	on btnEditFbxParams pressed do
	(
		createDialog boxFbxParams 250 480
	)
	
	/********************************************************************/

	group "静态物件"
	(
		button btnExportAllModel "导出所有"
		button btnExportSelectModel "导出选中"
	)

	on btnExportAllModel pressed do
	(
		setFbxParams false

		local currfilename = A2UFn_GenerateSMFilename true
		if currfilename == "" then currfilename = getFilenameFile maxFilename

		local filename = adjustPath outputPath + "\\"/*_getSkelParentPath()*/ + currfilename + "\\" + currfilename + ".fbx"
		local dir = getFilenamePath filename
		if not isDirectoryWriteable dir then makeDir dir all:true

		exportFile filename #noPrompt using:FBXEXP
	)

	on btnExportSelectModel pressed do
	(
		setFbxParams false
		
		local currfilename = A2UFn_GenerateSMFilename false
		if currfilename == "" then currfilename = getFilenameFile maxFilename

		local filename = adjustPath outputPath + "\\"/*_getSkelParentPath()*/ + currfilename + "\\" + currfilename + ".fbx"
		local dir = getFilenamePath filename
		if not isDirectoryWriteable dir then makeDir dir all:true

		exportFile filename #noPrompt selectedOnly:true using:FBXEXP
	)

	/********************************************************************/

	group "角色模型动画"
	(
		label lblSkel "体型名" align:#left across:2 offset:[0, 2]
		editText edtSkel "" align:#right width:100 offset:[0, 2]

		radiobuttons radFileType ""
		labels:#("模型","动画","多角色") columns:3 offset:[0,5] align:#center
		offsets:#([10,0], [0,0], [-10,0]) default:1

		button btnExportSkeleton "导出骨骼" offset:[0,4] enabled:true tooltip:"【仅限单角色】\n会将骨骼添加'SK_'前缀输出到\n'根目录/体型名/SK_体型名.fbx'" 
		button btnExportBatchModel "导出模型" enabled:true tooltip:"【仅限单角色】\n会将角色所有蒙皮模型输出到\n'根目录/体型名/体型名_套装名/蒙皮模型名.fbx'\n\n命名规范:      \nSK_体型名_套装名_部位名      \n例如：      \nSK_female_default01_body      \n布料片命名需要加 #cloth 后缀      \n例如：      \nSK_female_default01_body#cloth      \nSK_female_default01_body#cloth_back       \n多个布料部位可在 #cloth 后面自行添加名字\n"

		label seprator1 "———————————————" enabled:false
		button btnSingleAnim "导出动画" tooltip:"【仅限单角色】\n会将骨骼和动画FBX输出到\n'根目录/体型名/common/max文件名.fbx'" offset:[0, 2] enabled:false
		button btnBatchAnim "批量动画" tooltip:"【仅限单角色】\n对当前max所在文件夹的所有max文件，骨骼和动画FBX输出到\n'根目录/体型名/common/max文件名.fbx'" enabled:false
		button btnSelectCamAnim "输出选中相机" tooltip:"【仅限单角色】\n导出选中相机动画FBX，输出到\n'根目录/体型名/camera/相机名.fbx'" enabled:false
		button btnMultiCamAnim "输出所有相机" tooltip:"【仅限单角色】\n导出所有相机动画FBX，输出到\n'根目录/体型名/camera/相机名.fbx'" enabled:false

		label seprator2 "———————————————" enabled:false
		button btnSingleAnimMultiCharacter "多角色动画" tooltip:"【仅限多角色】\n把场景中所有骨架，依次按*skelid的体型名_ID名导出动画输出到\n'根目录/SKEL体型名/common/max文件名_ID名.fbx'" offset:[0, 2] enabled:false
		button btnBatchAnimMultiCharacter "批量多角色动画" tooltip:"【仅限多角色】\n对当前max所在文件夹的所有max文件，把场景中所有骨架，依次按*skelid的体型名_ID名导出动画输出到\n'根目录/SKEL体型名/common/max文件名_ID名.fbx'" enabled:false

		label seprator3 "———————————————" enabled:false
		checkbox chkExportMorphTargets "输出Morph动画" checked:false align:#center offset:[-2, 0] tooltip:"将有morph动画的模型一同导出，不符合输出规范的动画会被提示:\n1.只有2个关键帧以上的morph才会被导出\n2.超出当前动画范围的关键帧也会被导出\n（注意！没有添加关键帧的morph不会输出）"
		checkbox chkExportTransformData "动画防抖" across:2 checked:false align:#left offset:[2, 5] tooltip:"解决导入ue4动画抖动\n和FBX一同输出记录动画信息的同名txt文件"
		dropdownlist dplExportFrameRate checked:false align:#right width:60 offset:[-2, 3] items:#("60FPS", "120FPS", "240FPS") tooltip:"导出动画帧速率，尽量选小的值\n除非当前帧速率导入ue4中依然抖动，再选择下一级别（更高的级别会导致文件变大）"
	)
	
	local skelName = ""
	local exportFrameRate = 60
	
	on edtSkel entered text do
	(
		fileProperties.deleteProperty #custom "fbxskelpath"
		fileProperties.addProperty #custom "fbxskelpath" text
		skelName = edtSkel.text
	)

	on chkExportTransformData changed theState do
	(
		fileProperties.deleteProperty #custom "fbxexporttransformdata"
		fileProperties.addProperty #custom "fbxexporttransformdata" theState
		dplExportFrameRate.enabled = theState
	)

	on chkExportMorphTargets changed theState do
	(
		fileProperties.deleteProperty #custom "fbxexportmorph"
		fileProperties.addProperty #custom "fbxexportmorph" theState
	)

	fn _refreshExportFrameRate itemIndex = 
	(
		if itemIndex == 1 then
			exportFrameRate = 60
		else if itemIndex == 2 then
			exportFrameRate = 120
		else if itemIndex == 3 then
			exportFrameRate = 240
	)

	on dplExportFrameRate selected itemIndex do
	(
		fileProperties.deleteProperty #custom "fbxexportframerate"
		fileProperties.addProperty #custom "fbxexportframerate" itemIndex
		_refreshExportFrameRate itemIndex
	)

	fn _refreshFileType state =
	(
		if state == 1 then
		(
			lblSkel.enabled = true
			edtSkel.enabled = true
			btnExportSkeleton.enabled = true
			btnExportBatchModel.enabled = true
			btnSingleAnim.enabled = false
			btnBatchAnim.enabled = false
			btnSelectCamAnim.enabled = false
			btnMultiCamAnim.enabled = false
			btnSingleAnimMultiCharacter.enabled = false
			btnBatchAnimMultiCharacter.enabled = false
			chkExportMorphTargets.enabled = false
			chkExportTransformData.enabled = false
			dplExportFrameRate.enabled = false
		)
		else if state == 2 then
		(
			lblSkel.enabled = true
			edtSkel.enabled = true
			btnExportSkeleton.enabled = false
			btnExportBatchModel.enabled = false
			btnSingleAnim.enabled = true
			btnBatchAnim.enabled = true
			btnSelectCamAnim.enabled = true
			btnMultiCamAnim.enabled = true
			btnSingleAnimMultiCharacter.enabled = false
			btnBatchAnimMultiCharacter.enabled = false
			chkExportMorphTargets.enabled = true
			chkExportTransformData.enabled = true
			dplExportFrameRate.enabled = chkExportTransformData.checked
		)
		else
		(
			lblSkel.enabled = false
			edtSkel.enabled = false
			btnExportSkeleton.enabled = false
			btnExportBatchModel.enabled = false
			btnSingleAnim.enabled = false
			btnBatchAnim.enabled = false
			btnSelectCamAnim.enabled = false
			btnMultiCamAnim.enabled = false
			btnSingleAnimMultiCharacter.enabled = true
			btnBatchAnimMultiCharacter.enabled = true
			chkExportMorphTargets.enabled = true
			chkExportTransformData.enabled = true
			dplExportFrameRate.enabled = chkExportTransformData.checked
		)
	)

	on radFileType changed state do
	(
		fileProperties.deleteProperty #custom "fbxfiletype_gujian4"
		fileProperties.addProperty #custom "fbxfiletype_gujian4" state
		_refreshFileType state
	)

	fn refreshPath = 
	(
		-- 读不到数据就继承 按钮状态和体型名
		local pathid = fileProperties.findProperty #custom "fbxfiletype_gujian4"
		if pathid != 0 then 
		(
			radFileType.state = fileProperties.getPropertyValue #custom pathid
			_refreshFileType radFileType.state
		)
		
		pathid = fileProperties.findProperty #custom "fbxskelpath"
		if pathid != 0 then 
		(
			edtSkel.text = fileProperties.getPropertyValue #custom pathid
		)
		skelName = edtSkel.text
		
		-- 读不到数据就重置 fbx防抖和导出morph
		pathid = fileProperties.findProperty #custom "fbxexporttransformdata"
		if pathid != 0 then 
		(
			chkExportTransformData.checked = fileProperties.getPropertyValue #custom pathid
			dplExportFrameRate.enabled = chkExportTransformData.checked
		)
		else
		(
			chkExportTransformData.checked = false
			dplExportFrameRate.enabled = false
		)

		pathid = fileProperties.findProperty #custom "fbxexportframerate"
		if pathid != 0 then
		(
			dplExportFrameRate.selection = fileProperties.getPropertyValue #custom pathid
			_refreshExportFrameRate dplExportFrameRate.selection
		)
		else
		(
			dplExportFrameRate.selection = 1
			_refreshExportFrameRate dplExportFrameRate.selection
		)

		pathid = fileProperties.findProperty #custom "fbxexportmorph"
		if pathid != 0 then
		(
			chkExportMorphTargets.checked = fileProperties.getPropertyValue #custom pathid
		)
		else
		(
			chkExportMorphTargets.checked = true
		)
	)

	-- do export
	fn _getSkelParentPath =
	(
		return "\\"
	)
	
	fn _addSkeletonMesh =
	(
		local rectangleMeshName = skelName
		if (getNodeByName rectangleMeshName) != undefined then
			messagebox("所需Mesh已存在!      ")
		else
		(
			local SkinBoneNode = $Root
			if undefined == SkinBoneNode then
				messagebox("骨架上没有Root点!      ")
			else
			(
				local rectangleMesh = rectangle pos:[0,0,0] name:rectangleMeshName length:20 width:20
				addmodifier rectangleMesh (Skin())
				
				max modify mode
				
				select rectangleMesh
				skinOps.addbone rectangleMesh.Skin SkinBoneNode 0
			)
		)
	)

	fn getSkinModifierBoneNum geo = 
	(
		select geo
		for m in geo.modifiers where ClassOf m == Skin do
		(
			modPanel.setCurrentObject m
			local _bonesCount = skinOps.GetNumberBones m
			return _bonesCount
		)
		return 0
	)

	fn exportSingleSkeleton rootBone =
	(
		local skeletonMesh = getNodeByName skelName

		if undefined == skeletonMesh then
		(
			_addSkeletonMesh()
			skeletonMesh = getNodeByName skelName
		)
		else
		(
			max modify mode
			select skeletonMesh
			if not (A2UFn_CheckSkinModifierHaveBoneByName skeletonMesh "Root") then
			(
				delete skeletonMesh
				_addSkeletonMesh()
				skeletonMesh = getNodeByName skelName
			)
		)

		if undefined == skeletonMesh then
		(
			messagebox("未找到 导出体型对应的 面片Mesh!       ")
		)
		else
		(
			select skeletonMesh
			A2UFn_SelectAllSkeleton(rootBone)

			local skeletonPrefix = "SK_"
			local filename = adjustPath outputPath + _getSkelParentPath() + skelName + "\\" + skeletonPrefix + skelName + ".fbx"
			local dir = getFilenamePath filename
			if not isDirectoryWriteable dir then makeDir dir all:true

			exportFile filename #noPrompt selectedOnly:true using:FBXEXP
			
			messagebox("成功导出骨骼！     ")
		)
	)

	fn exportSingleModel rootBone geo folderName =
	(
		if (A2UFn_IsExportSkeletalMesh geo skelName) then
		(
			clearSelection()
			A2UFn_ApplyMaterialToAllClothMesh()
			A2UFn_SelectRelateClothMesh geo 	-- 选中Mesh和对应的布料片
			A2UFn_SelectAllSkeleton(rootBone)

			local filename = adjustPath outputPath + _getSkelParentPath() + skelName + "\\" + folderName + "\\" + geo.name + ".fbx"
			local dir = getFilenamePath filename
			if not isDirectoryWriteable dir then makeDir dir all:true
			exportFile filename #noPrompt selectedOnly:true using:FBXEXP
		)
	)
	
	-- 把*origin的运动套用到Root上，*origin不变，在导出后还原
	-- 如果 *origin没有关键帧 且 *origin/root在原点且没有旋转   就不要执行root解算步骤
	fn ExportSingleAnim rootBone skelName idName bNeedCalcRootMotion _bExportTransformData _TransformDataFPS _bExportMorph &_outputLog =
	(	
		local bipBone = A2UFn_GetBip01RootBySkelRoot rootBone
		local originBone = A2UFn_GetOriginNodeBySkelRoot rootBone

		if rootBone == undefined then
		(
			_outputLog = _outputLog + skelName + idName + "缺少Root!      \n"
			return false
		)
		else if bipBone == undefined then
		(
			_outputLog = _outputLog + skelName + idName + "缺少质心Bip!      \n"
			return false
		)
		else if originBone == undefined then
		(
			_outputLog = _outputLog + skelName + idName + "缺少*origin!      \n"
			return false
		)
		
		-- 计算RootMotion
		if bNeedCalcRootMotion then
		(
			--转换Root数据
			local myAnimationRangeStart = animationRange.start
			local myAnimationRangeEnd = animationRange.end

			-- 每一帧的Pos与Rot
			local originTransformList = #()			-- 用Transform不会出问题，单独用position/rotation会出问题
			local bipPosList_WorldSpace = #()
			local bipRotList_WorldSpace = #()

			-- *origin记录自己worldspace的transform，Bip01记录worldspace位置和parentspace的旋转z值
			for i = myAnimationRangeStart to myAnimationRangeEnd by 1 do
			(
				at time i
				(
					append originTransformList (in coordsys world originBone.transform)
					append bipPosList_WorldSpace (in coordsys world Biped.getTransform bipBone #pos)
					append bipRotList_WorldSpace (quatToEuler(in coordsys world Biped.getTransform bipBone #rotation))
				)
			)

			-- Root/*origin刷*origin的transform
			with animate on
			(
				for i = myAnimationRangeStart to myAnimationRangeEnd by 1 do
				(
					at time i
					(
						local _index = ((i.frame as integer) - myAnimationRangeStart + 1)
						rootBone.transform = originTransformList[_index]
						originBone.transform = originTransformList[_index]
					)
				)
			)

			-- bip01加一个动画层，刷一遍自己的位置和旋转
			local _animLayerIndex = (biped.numLayers bipBone.controller) + 1
			biped.createLayer bipBone.controller _animLayerIndex "RootMotionOffset"
			biped.setcurrentlayer bipBone.controller _animLayerIndex
			
			with animate on
			(
				for i = myAnimationRangeStart to myAnimationRangeEnd by 1 do
				(
					sliderTime = i
					at time i
					(
						local _index = ((i.frame as integer) - myAnimationRangeStart + 1)
						(in coordsys world Biped.setTransform bipBone #pos bipPosList_WorldSpace[_index] true)
						(in coordsys world Biped.setTransform bipBone #rotation bipRotList_WorldSpace[_index] true)
					)
				)

				-- 防止第一帧跳帧
				sliderTime = myAnimationRangeStart
				at time myAnimationRangeStart
				(
					(in coordsys world Biped.setTransform bipBone #pos bipPosList_WorldSpace[1] true)
					(in coordsys world Biped.setTransform bipBone #rotation bipRotList_WorldSpace[1] true)
				)
			)
		)

		-- 执行导出
		local animName = A2UFn_GetAnimFinalName (getFilenameFile maxFileName)
		if idName != "" then idName = ("_" + idName)
		local filename = adjustPath outputPath + "\\" + skelName + "\\common\\" + animName + idName + ".fbx"
		local dir = getFilenamePath filename
		if not isDirectoryWriteable dir then makeDir dir all:true

		sliderTime = animationRange.start
		clearSelection()
		A2UFn_SelectAllSkeleton(rootBone)
		
		if _bExportMorph then
		(
			local onlyOneMorphAnimNames = #()
			local outRangedMorphAnimNames = #()
			local animMorphNodes = A2UFn_SelectAllMorphAnimatedNode &onlyOneMorphAnimNames &outRangedMorphAnimNames

			if onlyOneMorphAnimNames.count > 0 or outRangedMorphAnimNames.count > 0 then
			(
				_outputLog = _outputLog + "\n【" + maxfilename + "】\n"
				_outputLog = _outputLog + "\n【体型ID：" + skelName + "_" + idName + "】\n"
				if onlyOneMorphAnimNames.count > 0 then
				(
					for _name in onlyOneMorphAnimNames do
					(
						_outputLog = _outputLog + "【只有一个morph关键帧】\n" + _name + "\n"
					)
				)
				if outRangedMorphAnimNames.count > 0 then
				(
					for _name in outRangedMorphAnimNames do
					(
						_outputLog = _outputLog + "【morph关键帧超出动画范围】\n" + _name + "\n"
					)
				)
				return false
			)
			else
			(
				selectmore animMorphNodes
			)			
		)
		
		exportFile filename #noPrompt selectedOnly:true using:FBXEXP

		if _bExportTransformData then
		(
			local _originFPS = framerate
			framerate = _TransformDataFPS
			local filename = adjustPath outputPath + "\\" + skelName + "\\common\\" + animName + idName + ".txt"
			A2UFn_ExportSkelAnimTransformData rootBone filename
			framerate = _originFPS
		)
		
		return true
	)

	-- 存导出相机的数据结构  name相加名 type相机类型 nodes相机和相机Target(如果有)的所有node，用来导出时选中
	struct cam_set (name, type, nodes)

	-- bUseValidNames是否只导出传入名字的相机 _validNamesArr到处相机的名字数组
	fn ExportMultiCamAnim bUseValidNames _validNamesArr = 
	(
		-- 记录FBX导出参数
		local _bakeAnimation = fbxExporterGetParam "BakeAnimation"
		local _cameras = fbxExporterGetParam "Cameras"
		local _upAxis = fbxExporterGetParam "UpAxis"
		
		-- 设置FBX导出参数 不需要Bake动画 需要导出相机
		fbxExporterSetParam "BakeAnimation" false
		fbxExporterSetParam "Cameras" true
		
		local allCameras = #()
		
		-- $cameras包含了相机和相机的Taget物体，这里过滤出真正的相机
		for _node in $Cameras do
		(
			if isKindOf _node camera then
				append allCameras _node
		)

		local vaildCameras = #()
		if bUseValidNames then
		(
			for cam in allCameras do
			(
				if findItem _validNamesArr cam.name > 0 then
				(
					append vaildCameras cam
				)
			)
		)
		else
		(
			vaildCameras = allCameras
		)
		
		local exportCamSets = #()
		for cam in vaildCameras do
		(
			local camSetNodes = #()
			-- 加入相机本身
			append camSetNodes cam
			-- 加入相机Target
			for _node in $Cameras do
			(
				if A2UFn_IsCameraTarget(_node) then			-- 选中所有Target物体,寻找当前相机对应的Target
				(
					local _name = (filterString _node.name ".")[1]
					if _name == cam.name then
						append camSetNodes _node
				)
			)

			local newCamSet = cam_set cam.name (classOf cam) camSetNodes
			append exportCamSets newCamSet
		)
		
		for i = 1 to exportCamSets.count do
		(
			-- 设置 UpAxis 两种相机不一样
			if exportCamSets[i].type == Targetcamera then
			(
				fbxExporterSetParam "UpAxis" "Z"
			)
			else if exportCamSets[i].type == Physical then
			(
				fbxExporterSetParam "UpAxis" "Z"
			)
			else if exportCamSets[i].type == Freecamera then
			(
				fbxExporterSetParam "UpAxis" "Y"
			)
			else
			(
				messageBox "失败2 相机类型判断错误！     "
				return false
			)

			clearSelection()
			select exportCamSets[i].nodes
			
			local filename = adjustPath outputPath + _getSkelParentPath() + skelName + "\\camera\\" + exportCamSets[i].name + ".fbx"
			local dir = getFilenamePath filename

			if not isDirectoryWriteable dir then makeDir dir all:true
			exportFile filename #noPrompt selectedOnly:true using:FBXEXP
		)

		-- 还原FBX导出参数
		fbxExporterSetParam "BakeAnimation" _bakeAnimation
		fbxExporterSetParam "Cameras" _cameras
		fbxExporterSetParam "UpAxis" _upAxis
		return true
	)

	fn ExportSelectCamAnim selNodes = 
	(
		local selCamNamesArr = #()
		for _node in selNodes do
		(
			if (A2UFn_IsCameraOrCameraTarget _node) then
			(
				local _name = (filterString _node.name ".")[1]
				appendIfUnique selCamNamesArr _name
			)
		)
		
		if selCamNamesArr.count <= 0 then
		(
			messageBox("请选中至少一个相机      ")
			return false
		)
		else
		(
			ExportMultiCamAnim true selCamNamesArr
		)
		messageBox("输出选中相机成功！      ")
	)

	-- 如果有多个Root 也只会选中输出最先创建的那个Root
	fn ExportSingleCharacterAnim &outputLog &namingInvalid = 
	(
		setFbxParams true
		local ret = false
		local skelRoot = $Root			-- 如果有多个Root 也只会选中输出最先创建的那个Root
		local skelRootNum = A2UFn_GetRootNodeNum()
		
		-- 命名规则前缀检查
		if A2UFn_CheckMaxFileNamePrefix("AS_") == false then
		(
			namingInvalid = namingInvalid + (getFilenameFile maxFileName) + ".max\n"
		)

		if outputPath == "" then
		(
			outputLog = outputLog + "【" + maxfilename + "】\n工作路径未设置\n"
		)
		else if skelName == "" then
		(
			outputLog = outputLog + "【" + maxfilename + "】\n体型路径未设置\n"
		)
		else if skelRoot == undefined then
		(
			outputLog = outputLog + "【" + maxfilename + "】\n场景中没有有效的Root骨架\n"
		)
		else
		(
			-- 是否解算RootMotion
			local bNeedCalcRootMotion = A2UFn_NeedCalcRootMotion skelRoot
			-- 是否还原*resetlink点
			local bHaveResetLink = A2UFn_TryResetAllNodeLinksByRoot skelRoot
			
			local bNeedRestoreFile = bNeedCalcRootMotion or bHaveResetLink

			-- RootMotion / *resetlink 暂存
			if bNeedRestoreFile then
			(
				holdMaxFile()
				if not GetQuietMode() then setquietmode true
			)

			if skelRootNum > 1 then
				outputLog = outputLog + "\n【注意】      \n场景中有超过一个Root，需检查是否选中了正确的骨架      \n已选中当前输出的骨架      \n"

			ret = ExportSingleAnim skelRoot skelName "" bNeedCalcRootMotion chkExportTransformData.checked exportFrameRate chkExportMorphTargets.checked &outputLog
			
			-- RootMotion还原
			if bNeedRestoreFile then
			(
				fetchMaxFile()
				if not GetQuietMode() then setquietmode false				
				skelRoot = $Root
			)

			clearSelection()
			A2UFn_SelectAllSkeleton(skelRoot)
		)
		return ret
	)

	fn ExportMultiCharacterAnim &outputLog &namingInvalid = 
	(
		setFbxParams true
		local ret = false
		local skelRoots = A2UFn_GetAllSkelRoot()
		local exportSkelRoots = #()
		local skelidNames = #()
		local exportSkelCount = 0
		local nonExportSkelCount = 0

		-- 命名规则前缀检查
		if A2UFn_CheckMaxFileNamePrefix("AS_") == false then
		(
			namingInvalid = namingInvalid + (getFilenameFile maxFileName) + ".max\n"
		)

		for skelRoot in skelRoots do
		(
			local skelIDName = A2UFn_GetSkelIDNameBySkelRoot(skelRoot)
			if skelIDName != "" then
			(
				appendIfUnique skelidNames skelIDName
				append exportSkelRoots skelRoot
				exportSkelCount = exportSkelCount + 1
			)
			else
			(
				nonExportSkelCount = nonExportSkelCount + 1
			)
		)

		if exportSkelCount < 1 then
			outputLog = outputLog + "【" + maxfilename + "】\n没有包含*skelid的骨架\n"
		else if skelidNames.count != exportSkelCount then
			outputLog = outputLog + "【" + maxfilename + "】\n有同名的*skelid体型ID名\n" 
		else
		(
			ret = true

			-- 检查SkelID名字是否格式正确
			local inValidSkelIDNames = #()
			for skelRoot in exportSkelRoots do
			(
				local skelidName = A2UFn_GetSkelIDNameBySkelRoot(skelRoot)
				local skelidNameValid = A2UFn_CheckSkelIDNameValid(skelidName)
				if not skelidNameValid then
				(
					append inValidSkelIDNames skelidName
				)
			)

			if inValidSkelIDNames.count > 0 then
			(
				ret = false
				outputLog = outputLog + "【" + maxfilename + "】\n"
				for invalidName in inValidSkelIDNames do
				(
					outputLog = outputLog + invalidName + "  体型ID名不符合规范     \n"
				)
			)
			else
			(
				-- 是否至少有一个体型 需要解算RootMotion
				local bNeedCalcRootMotion = false
				-- 是否至少有一个体型 需要ResetLink
				local bHaveResetLink = false

				for skelRoot in exportSkelRoots do
				(
					if A2UFn_NeedCalcRootMotion skelRoot then
						bNeedCalcRootMotion = true
					if A2UFn_TryResetAllNodeLinksByRoot skelRoot then
						bHaveResetLink = true
				)

				local bNeedRestoreFile = bNeedCalcRootMotion or bHaveResetLink

				-- RootMotion暂存
				if bNeedRestoreFile then
				(
					holdMaxFile()
					if not GetQuietMode() then setquietmode true
				)

				-- 依次导出
				for skelRoot in exportSkelRoots do
				(
					local skelidName = A2UFn_GetSkelIDNameBySkelRoot(skelRoot)
					local skelName = ""
					local idName = ""
					local _nameArr = A2UFn_GetSkelNameAndIDNameBySkelIDName skelidName &skelName &idName
					local _curSkelNeedCalcRootMotion = A2UFn_NeedCalcRootMotion skelRoot
					local _ret = ExportSingleAnim skelRoot skelName idName _curSkelNeedCalcRootMotion chkExportTransformData.checked exportFrameRate chkExportMorphTargets.checked &outputLog

					if _ret = false then
						ret = false
					else
						outputLog = outputLog + "【成功】输出多角色动作：" + skelidName + "      \n"
				)
				
				-- RootMotion还原
				if bNeedRestoreFile then
				(
					fetchMaxFile()
					if not GetQuietMode() then setquietmode false
				)
			)
		)
		return ret
	)

	on btnExportSkeleton pressed do
	(
		refreshPath()
		if outputPath == "" then
			messagebox("FBX导出根目录 未设置     ")
		else if skelName == "" then
			messagebox("体型名 未设置     ")
		else 
		(
			local skelRoots = A2UFn_GetAllSkelRoot()
			if skelRoots.count < 1 then
			(
				messageBox("导出场景中没有有效的Root骨架      ")
				return false
			)
			else if skelRoots.count > 1 then
			(
				messageBox("导出场景中有超过一个Root骨架      ")
				return false
			)
	
			setFbxParams false
			exportSingleSkeleton($Root)
		)
	)
	
	on btnExportBatchModel pressed do
	(
		refreshPath()
		if outputPath == "" then
			messagebox("FBX导出根目录 未设置     ")
		else if skelName == "" then
			messagebox("体型名 未设置     ")
		else 
		(
			local skelRoots = A2UFn_GetAllSkelRoot()
			if skelRoots.count < 1 then
			(
				messageBox("导出模型场景中没有有效的Root骨架      ")
				return false
			)
			else if skelRoots.count > 1 then
			(
				messageBox("导出模型场景中有超过一个Root骨架      ")
				return false
			)

			setFbxParams false
			clearSelection()
			max modify mode
			
			local skelRoot = skelRoots[1]
			local singleModelMaxBoneNum = 65536
			local unvalidModels = #()
			local unvalidModelOutput = ""

			for geo in geometry do
			(
				if A2UFn_IsBoneOrBiped geo then
					continue
				
				local boneNum = getSkinModifierBoneNum geo
				if boneNum != undefined and boneNum > singleModelMaxBoneNum then
				(
					append unvalidModels geo
					unvalidModelOutput = unvalidModelOutput + "\n 骨骼数：" + boneNum as string + "\t骨骼名：" + geo.name
				)
			)
			
			-- 如果有不满足条件的模型，列出并选中它们
			if unvalidModels.count > 0 then
			(
				clearSelection()
				for item in unvalidModels do
				(
					selectmore item
				)
				messageBox ("导出模型     \n因为以下模型绑定了超过" + singleModelMaxBoneNum as string + "根骨骼：\n" + unvalidModelOutput + "\n\n已经选中以上模型       ")
			)
			else
			(
				local _geoToExport = #()
				local _invalidModelNames = ""

				for geo in geometry do
				(
					if (A2UFn_IsExportSkeletalMesh geo skelName) then
						append _geoToExport geo
				)

				for geo in _geoToExport do
				(
					local folderName = A2UFn_GetModelSkelAndSetName geo
					if folderName == "" then
						_invalidModelNames = _invalidModelNames + geo.name + "\n"
				)

				for geo in _geoToExport do
				(
					local folderName = A2UFn_GetModelSkelAndSetName geo
					exportSingleModel skelRoot geo folderName
				)

				if _invalidModelNames != "" then
				(
					messagebox ("导出模型成功！     \n\n【注意】      \n以下模型命名不符合规范 (规范见按钮tips)：      \n" + _invalidModelNames + "      \n")
				)
				else
				(
					messageBox "导出模型成功！     "
				)
			)
		)
	)
	
	on btnSingleAnim pressed do
	(
		refreshPath()
		local outputLog = ""
		local namingInvalid = ""

		--START
		local ret = ExportSingleCharacterAnim &outputLog &namingInvalid
		--END
		
		local msgText = ""
		if ret == true then
		(
			msgText = "单个动画输出成功！     \n" + outputLog
			if namingInvalid != "" then
			(
				msgText = msgText + ("\n【注意】      \n以下文件没有 AS_ 前缀：      \n" + namingInvalid)
			)
		)
		else
		(
			msgText = ("单个动画输出     \n" + outputLog)
		)
		messageBox msgText
	)

	on btnBatchAnim pressed do
	(
		refreshPath()
		setquietmode true
		
		local outputLog = ""
		local namingInvalid = ""
		local ret = true
		
		for f in getfiles (maxFilePath + "\\*.max") do 
		(
			loadMaxFile f usefileunits:true
			
			--START
			local retCurFile = ExportSingleCharacterAnim &outputLog &namingInvalid
			--END
			
			if retCurFile == false then
				ret = false		-- 当前max文件输出失败
		)
		setquietmode false
		
		local msgText = ""
		if ret == true then
		(
			msgText = "批量动画输出成功！     \n"
			if namingInvalid != "" then
			(
				msgText = msgText + ("\n【注意】      \n以下文件没有 AS_ 前缀：      \n" + namingInvalid)
			)
		)
		else
		(
			msgText = ("以下动画输出     \n" + outputLog)
		)
		messageBox msgText
	)

	on btnSelectCamAnim pressed do
	(
		refreshPath()
		if outputPath == "" then
			messagebox("工作路径未设置     ")
		else if skelName == "" then
			messagebox("体型路径未设置     ")
		else
		(
			setFbxParams true
			ExportSelectCamAnim $selection
		)
	)
	
	on btnMultiCamAnim pressed do
	(
		refreshPath()
		if outputPath == "" then
			messagebox("工作路径未设置     ")
		else if skelName == "" then
			messagebox("体型路径未设置     ")
		else
		(
			if $Cameras.count < 1 then
			(
				messageBox("导出场景中没有有效的相机      ")
				return false
			)

			setFbxParams true
			ExportMultiCamAnim false ""

			messageBox("输出所有相机成功！      ")
		)
	)

	on btnSingleAnimMultiCharacter pressed do
	(
		refreshPath()

		local outputLog = ""
		local namingInvalid = ""

		--START
		local ret = ExportMultiCharacterAnim &outputLog &namingInvalid
		--END

		local msgText = ""
		if ret == true then
		(
			msgText = "【多角色动画】输出成功！     \n" + outputLog
			if namingInvalid != "" then
			(
				msgText = msgText + ("\n【注意】      \n以下文件没有 AS_ 前缀：      \n" + namingInvalid)
			)
		)
		else
		(
			msgText = ("【多角色动画】输出     \n" + outputLog)
		)
		messageBox msgText
	)
	
	on btnBatchAnimMultiCharacter pressed do
	(
		refreshPath()
		if outputPath == "" then
			messagebox("工作路径未设置     ")
		else
		(
			setquietmode true
			local outputLog = ""
			local namingInvalid = ""
			local ret = true
			
			for f in getfiles (maxFilePath + "\\*.max") do 
			(
				loadMaxFile f usefileunits:true
				
				--START
				local retCurFile = ExportMultiCharacterAnim &outputLog &namingInvalid
				--END

				if retCurFile == false then
					ret = false		-- 当前max文件输出失败
			)
			setquietmode false
			
			local msgText = ""
			if ret == true then
			(
				msgText = "【批量多角色动画】输出成功！      \n" + outputLog
				if namingInvalid != "" then
				(
					msgText = msgText + ("\n【注意】      \n以下文件没有 AS_ 前缀：      \n" + namingInvalid)
				)
			)
			else
			(
				msgText = ("【批量多角色动画】输出      \n" + outputLog)
			)
			messageBox msgText
		)
	)

	/********************************************************************/

	on Gujian4_ExportFbxUtil open do
	(
		outputPath = A2UFn_GetFBXExportPath_Gujian4()
		edtOutputRoot.text = outputPath
		refreshPath()
		
		callbacks.addscript #filePostOpen"Gujian4_ExportFbxUtil.refreshPath()" id:#testCallback
	)
	
	on Gujian4_ExportFbxUtil close do
	(
		callbacks.removeScripts id:#testCallback
	)
)

rollout Gujian4_ClothUtil "布料工具"
(
	button btnCloth_SetMaterial "设置材质" align:#center toolTip:"为所有代理布料Mesh设置独立材质" width:100
	button btnCloth_CheckSkin "检查蒙皮" align:#center toolTip:"检查代理布料Mesh的蒙皮是否添加" width:100

	on btnCloth_SetMaterial pressed do
	(
		local clothMeshes = A2UFn_GetAllClothMesh()
		for geo in clothMeshes do
		(
			local newMat = StandardMaterial()
			newMat.name = geo.name
			geo.material = newMat
		)
		messagebox("所有 代理布料Mesh 设置材质成功！     ")
	)

	on btnCloth_CheckSkin pressed do
	(
		local clothMeshes = A2UFn_GetAllClothMesh()
		local outputLog = ""
		for geo in clothMeshes do
		(
			if geo.modifiers["skin"] == undefined then
			(
				outputLog = outputLog + geo.name + "     \n"
			)
		)
		outputLog = outputLog + "     \n"

		if outputLog == "" then
		(
			messagebox("所有 代理布料Mesh 都设置了Skin修改器！     ")
		)
		else
		(
			messagebox("以下代理布料Mesh 没有设置Skin修改器！     \n" + outputLog)
		)
	)
)

rollout Gujian4_VertexColorUtil "顶点色工具"
(
	label lblVC_Bake_SM "烘焙顶点色（SM物件）" align:#left
	button btnVC_Bake_SM "执行" align:#center toolTip:"烘焙法线方向到顶点色RGB通道" width:90
	label lblVC_Reset_SK "保留红色顶点色（SK角色）" align:#left
	button btnVC_Reset_SK "执行" align:#center toolTip:"保留顶点色中的红色部分(0~255,0,0)，便于调整描边粗细" width:90

	label lblVC_Bake_Color "快速刷顶点色:" align:#left offset:[0,6]
	button btnVC_Bake_White "白" align:#center toolTip:"刷顶点色为(255,255,255)" width:24 across:5 offset:[0,2]
	button btnVC_Bake_Red "红" align:#center toolTip:"刷顶点色为(255,0,0)" width:24 offset:[0,2]
	button btnVC_Bake_Green "绿" align:#center toolTip:"刷顶点色为(0,255,0)" width:24 offset:[0,2]
	button btnVC_Bake_Blue "蓝" align:#center toolTip:"刷顶点色为(0,0,255)" width:24 offset:[0,2]
	button btnVC_Bake_Black "黑" align:#center toolTip:"刷顶点色为(0,0,0)" width:24 offset:[0,2]

	fn CheckCanConvert curNode = 
	(
		-- 如果不是Polygon 则不允许操作
		if ClassOf curNode.baseObject != Editable_Poly then
		(
			messagebox("请先将" + curNode.name + "转换为Polygon     \n")
			return false
		)
		-- 如果有VertexPaint修改器 提示先塌陷修改器
		for m in curNode.modifiers where ClassOf m == VertexPaint do
		(
			messagebox("请先塌陷掉" + curNode.name + "的VertexPaint修改器     \n")
			return false
		)
		
		return true
	)
	
	fn AddVertPaintModifier_SetVertColor curNode paintColor = 
	(
		max modify mode
		modPanel.setCurrentObject curNode.baseObject
		modPanel.addModToSelection (VertexPaint ())
		local CollpaseID = curNode.modifiers.count
		maxOps.CollapseNodeTo curNode CollpaseID true
		local curVerts = (polyop.getNumVerts curNode)
		for i = 1 to curVerts do
		(
			polyop.setVertColor curNode 0 #{i} paintColor
		)
	)
	
	fn ResetVertexColorRed curNode = 
	(
		-- 如果没有CPV点就要先增加VertexPaint修改器 并将它塌陷掉
		if curNode.mesh.numcpvverts == 0 then
		(
			AddVertPaintModifier_SetVertColor curNode (color 255 255 255)
		)
		
		-- 先获取顶点和CPV点映射关系，因为顶点色只能通过mesh的CPV点获取颜色
		local vertToCPVVert = #()
		for i = 1 to curNode.mesh.numfaces do
		(
			local face = getFace curNode.mesh i
			local vcface = getVCFace curNode.mesh i

			print ("Errface " + i as string)
			if vcface == undefined then continue
			if face == undefined then continue
			
			vertToCPVVert[face[1]] = vcface[1]
			vertToCPVVert[face[2]] = vcface[2]
			vertToCPVVert[face[3]] = vcface[3]
		)

		local curVerts = (polyop.getNumVerts curNode)
		for i = 1 to curVerts do
		(
			local cpvIndex = vertToCPVVert[i]
			local vc = getVertColor curNode.mesh cpvIndex
			polyop.setVertColor curNode 0 #{i} (color vc.r 0 0)
		)
	)

	fn SetSelectNodesVertexColor paintColor = 
	(
		local arrSelect = getCurrentSelection()
		if arrSelect.count <= 0 then
		(
			messagebox("未选中任何物体！     ")
			return false
		)
		-- 检查所选Node是否符合条件
		for curNode in arrSelect do
		(
			local ret = CheckCanConvert curNode
			if ret != true then
			(
				return false
			)
		)
		-- 刷新顶点色
		for curNode in arrSelect do
		(
			AddVertPaintModifier_SetVertColor curNode paintColor
		)
		return true
	)
	
	fn SetVertexColorLocalAvrageNormal curNode = 
	(
		-- 如果没有CPV点就要先增加VertexPaint修改器 并将它塌陷掉
		if curNode.mesh.numcpvverts == 0 then
		(
			AddVertPaintModifier_SetVertColor curNode (color 255 255 255)
		)

		local curNormal = #()
		local curVerts = (polyop.getNumVerts curNode)
		for i = 1 to curVerts do
		(
			local tFace = polyop.getFacesUsingVert curNode #(i)
			tFace = tFace as Array
			local tempNormalTotal = [0, 0, 0]
			for j = 1 to tFace.count do
			(
				tempNormalTotal += normalize(polyop.getFaceNormal curNode tFace[j])
			)
			tempNormalTotal = normalize(tempNormalTotal / tFace.count)
			-- ue4中y方向和3dsmax中相反，所以加负号
			append curNormal (tempNormalTotal * [1, -1, 1])
		)
		
		for i = 1 to curVerts do
		(
			local dirColor = (curNormal[i] + [1,1,1]) / 2 * 255
			local colR = dirColor.x												-- R（0~255）
			local colG = dirColor.y												-- G（0~255）
			local colB = dirColor.z												-- B（0~255）
			
			polyop.setVertColor curNode 0 #{i} (color colR colG colB)
		)
	)
	
	on btnVC_Bake_SM pressed do
	(
		local arrSelect = getCurrentSelection()
		if arrSelect.count <= 0 then
		(
			messagebox("未选中任何物体！      ")
			return false
		)
		-- 检查所选Node是否符合条件
		for curNode in arrSelect do
		(
			local ret = CheckCanConvert curNode
			if ret != true then
			(
				return false
			)
		)
		-- 刷新顶点色
		for curNode in arrSelect do
		(
			SetVertexColorLocalAvrageNormal curNode
		)
		messagebox("烘焙SM物件顶点色完成!      ")
		return true
	)

	on btnVC_Reset_SK pressed do
	(
		local arrSelect = getCurrentSelection()
		if arrSelect.count <= 0 then
		(
			messagebox("未选中任何物体！      ")
			return false
		)
		-- 检查所选Node是否符合条件
		for curNode in arrSelect do
		(
			local ret = CheckCanConvert curNode
			if ret != true then
			(
				return false
			)
		)
		-- 刷新顶点色
		for curNode in arrSelect do
		(
			ResetVertexColorRed curNode
		)
		messagebox("还原角色顶点色完成!      ")
		return true
	)

	on btnVC_Bake_White pressed do
	(
		local ret = SetSelectNodesVertexColor (color 255 255 255)
		if ret == true then
			messagebox("重置顶点色为白色完成!      ")
	)

	on btnVC_Bake_Red pressed do
	(
		local ret = SetSelectNodesVertexColor (color 255 0 0)
		if ret == true then
			messagebox("重置顶点色为红色完成!      ")
	)

	on btnVC_Bake_Green pressed do
	(
		local ret = SetSelectNodesVertexColor (color 0 255 0)
		if ret == true then
			messagebox("重置顶点色为绿色完成!      ")
	)

	on btnVC_Bake_Blue pressed do
	(
		local ret = SetSelectNodesVertexColor (color 0 0 255)
		if ret == true then
			messagebox("重置顶点色为蓝色完成!      ")
	)

	on btnVC_Bake_Black pressed do
	(
		local ret = SetSelectNodesVertexColor (color 0 0 0)
		if ret == true then
			messagebox("重置顶点色为黑色完成!      ")
	)
	
)

rollout Gujian4_AnimToolsUtil "动画工具"
(
	
	rollout Gujian4_SkinBoneCheckResult "蒙皮骨骼检查"
	(
		local tNodeListBoxItems = #()
		local tBoneListBoxItems = #()
		local tBoneListUnusedBones = #()
		local tSkinBoneCheckSelection = #()

		group ""
		(
			label nodeListTitle "模型列表：" align:#left across:2
			label boneListTitle "无效骨骼列表：" align:#right offset:[-196,0]
			listbox nodeListBox width:420 height:25 align:#left across:2
			listbox boneListBox width:280 height:25 align:#right
		)
		-- offset:[20,-114]
		group "1.先检查修改器Unique"
		(
			label removeUnusedText "使用移除无效骨骼前，必须保证每个模型的蒙皮修改器（Skin）为Unique！\n可使用右边的按钮选中列表中所有物体，逐个在右上角修改器（Modifier）中检查" align:#left height:36 across:2
			button btnSelectListNodes "选中列表中所有模型" align:#left offset:[100,2] width:200 height:30
		)

		group "2.再移除无效骨骼"
		(
			button btnRemoveSingle "移除列表 选中模型 无效骨骼" align:#left offset:[50,2] width:270 height:30 across:2
			button btnRemoveAll "移除列表 全部模型 无效骨骼" align:#right offset:[-50,2] width:270 height:30
		)

		fn RemoveSkinUnusedBone nodeID = 
		(
			local curNode = tSkinBoneCheckSelection[nodeID]
			clearSelection()
			select curNode

			for m in curNode.modifiers where ClassOf m == Skin do
			(
				-- Remove Bone
				modPanel.setCurrentObject m
				for i = tBoneListUnusedBones[nodeID].count to 1 by -1 where tBoneListUnusedBones[nodeID][i] do
				(
					skinOps.SelectBone m i
					skinOps.RemoveBone m
				)
				-- Refresh Data/UI
				local vertCount = skinOps.GetNumberVertices m
				local boneCount = skinOps.GetNumberBones m
				local unusedBones = #{1..boneCount}

				for v = 1 to vertCount do
				(
					local vertWeightBoneCount = skinOps.GetVertexWeightCount m v
					for b = 1 to vertWeightBoneCount do
					(
						local boneWeight = skinOps.GetVertexWeight m v b
						if boneWeight > 0 then
						(
							local boneID = skinOps.GetVertexWeightBoneID m v b
							if boneID <= 0 then
							(
								return false
							)
							unusedBones[boneID] = false
						)
					)
				)
				
				tBoneListBoxItems[nodeID] = #()
				tBoneListUnusedBones[nodeID] = unusedBones
				
				local unusedBoneNum = 0
				for j = boneCount to 1 by -1 where unusedBones[j] do
				(
					unusedBoneNum = unusedBoneNum + 1
					append tBoneListBoxItems[i] ("\n\t" + skinOps.GetBoneName m j 0)
				)

				tNodeListBoxItems[nodeID] = "模型名：" + curNode.name + "\t（" + (boneCount as string) + "根骨骼 / " + (unusedBoneNum as string) + "根无效骨骼）\n"
				return true
			)
		)

		fn InitData _tNodeListBoxItems _tBoneListBoxItems _tBoneListUnusedBones _tSkinBoneCheckSelection = 
		(
			tNodeListBoxItems = _tNodeListBoxItems
			tBoneListBoxItems = _tBoneListBoxItems
			tBoneListUnusedBones = _tBoneListUnusedBones
			tSkinBoneCheckSelection = _tSkinBoneCheckSelection
			
			nodeListBox.items = tNodeListBoxItems
			if tBoneListBoxItems.count > 0 then
				boneListBox.items = tBoneListBoxItems[1]
			else
				boneListBox.items = #()
		)

		on nodeListBox selected id do
		(
			if id > tNodeListBoxItems.count then
				return ""
			boneListBox.items = tBoneListBoxItems[id]
		)

		on btnRemoveSingle pressed do
		(
			max hold
			if RemoveSkinUnusedBone (nodeListBox.selection) == false then
			(
				DestroyDialog Gujian4_SkinBoneCheckResult
				setquietmode true
				max fetch
				setquietmode false
				messageBox ("操作请检查是否有被多个模型同时使用的Skin修改器       ")
				return ""
			)
			-- Refresh UI when Success
			messageBox("成功移除物体" + tSkinBoneCheckSelection[nodeListBox.selection].name + "的无效骨骼      ")
			nodeListBox.items = tNodeListBoxItems
			boneListBox.items = tBoneListBoxItems[nodeListBox.selection]
		)

		on btnRemoveAll pressed do
		(
			max hold
			for nodeID = 1 to nodeListBox.items.count do
			(
				if (RemoveSkinUnusedBone nodeID) == false then
				(
					DestroyDialog Gujian4_SkinBoneCheckResult
					max fetch
					messageBox ("操作请检查是否有被多个模型同时使用的Skin修改器       ")
					return ""
				)
			)
			-- Refresh UI when Success
			messageBox("成功移除列表中全部物体的无效骨骼      ")
			nodeListBox.items = tNodeListBoxItems
			boneListBox.items = tBoneListBoxItems[nodeListBox.selection]
		)

		on btnSelectListNodes pressed do
		(
			clearSelection()
			select tSkinBoneCheckSelection
		)
	)

	local tNodeListBoxItems = #()
	local tBoneListBoxItems = #()
	local tBoneListUnusedBones = #()
	local tSkinBoneCheckSelection = #()
	
	group "物体跟随计算"	--NF: node follow
	(
		label lblNF_NodeA "目标物体A" align:#left across:2
		button btnNF_NodeA "填充名称" align:#right toolTip:"填充选中的物体名称" offset:[0,-4]
		editText edtNF_NodeA "" offset:[0, -1]
		label lblNF_NodeB "跟随物体B" align:#left across:2
		button btnNF_NodeB "填充名称" align:#right toolTip:"填充选中的物体名称" offset:[0,-4]
		editText edtNF_NodeB "" offset:[0, -1]
		
		checkbox chkNF_Pos "位移:" align:#left offset:[-5,0] across:4 
		checkbox chkNF_UseX "x" align:#left offset:[13,0]
		checkbox chkNF_UseY "y" offset:[13,0]
		checkbox chkNF_UseZ "z" offset:[13,0]/* align:#left across:3
		label lblNF_ZValue "z距离:" align:#left
		editText edtNF_ZValue "" align:#right width:60 offset:[0, -1]*/
		
		checkbox chkNF_Rot "旋转:" align:#left offset:[-5,0] across:4 
		checkbox chkNF_UseRoll "x" align:#left offset:[13,0]
		checkbox chkNF_UsePitch "y" offset:[13,0]
		checkbox chkNF_UseYaw "z" offset:[13,0]
		
		button btnNF_Calc "计算" tooltip:"将物体A的相对运动轨迹，拷贝给物体B" width:80
	)
	
	group "父子跟随运动"	--PF: follow parent
	(
		label lblFP_NodeA "目标父物体A" align:#left across:2 
		button btnFP_NodeA "填充名称" align:#right toolTip:"填充选中的物体名称" offset:[0,-4]
		editText edtFP_NodeA "" offset:[0, -1]
		label lblFP_NodeB "跟随物体B" align:#left across:2 
		button btnFP_NodeB "填充名称" align:#right toolTip:"填充选中的物体名称" offset:[0,-4]
		editText edtFP_NodeB "" offset:[0, -1]
		
		button btnFP_Calc "计算" tooltip:"将物体B的运动轨迹，改为作为物体A子物体的运动轨迹（不会改变B的父物体）" width:80
	)

	group "骨骼重叠缩放关键点"	--检查是否有同一Frame有多个关键点的情况
	(
		button btn_SingleCheckOverlapFrame "检查当前文件" tooltip:"检查检查当前max文件，骨骼同一帧是否有多个Scale关键点的情况，如果有将移除多余的关键点" width:80
		button btn_BatchCheckOverlapFrame "批量检查并修复" tooltip:"批量检查相同文件夹下的所有max文件，如果有多余的关键点会修复并保存" width:100
	)

	group "检查蒙皮骨骼"
	(
		button btn_CheckUnusedBone "检查选中物件" tooltip:"检查选中模型的蒙皮的骨骼数，是否有无效骨骼（骨骼权重为0）" width:80
	)
	
	-- 物体跟随计算 NF
	fn __refreshChkPos = 
	(
		if chkNF_UseX.state or 
		chkNF_UseY.state or 
		chkNF_UseZ.state then
			chkNF_Pos.state = false
		else
			chkNF_Pos.state = true
	)

	fn DeleteOverlapScaleKey _Node &_anyboneoverlapped &_bonecount = 
	(
		if (hasProperty _Node.controller #scale) then
		(
			if (_Node.controller.scale.keys != undefined and _Node.controller.scale.keys.count > 0) then
			(
				local _lastKeyFrameTime = undefined
				local _bOverlapped = false
				for i = 1 to _Node.controller.scale.keys.count do
				(
					if _Node.controller.scale.keys[i].time == _lastKeyFrameTime then
					(
						selectKey _Node.controller.scale.controller i
						_bOverlapped = true
						print (_Node.name + " has overlapped scale keys at FRAME:" + _Node.controller.scale.keys[i].time as string)
					)
					_lastKeyFrameTime = _Node.controller.scale.keys[i].time
				)
				deleteKeys _Node.controller.scale.controller #selection

				if _bOverlapped then
				(
					_bonecount = _bonecount + 1
					_anyboneoverlapped = true
				)
			)
		)
	)

	fn CheckSelectBone = 
	(
		if selection.count <= 0 then	
		(
			messageBox "请至少选择一个物体      "
			return ""
		)
		
		max modify mode
		tSkinBoneCheckSelection = #()
		tNodeListBoxItems = #()
		tBoneListBoxItems = #()
		tBoneListUnusedBones = #()

		for i = 1 to (selection as array).count do
		(
			append tSkinBoneCheckSelection (selection as array)[i]
		)
		
		for i = 1 to tSkinBoneCheckSelection.count do
		(
			tBoneListBoxItems[i] = #()
			clearSelection()
			select tSkinBoneCheckSelection[i]
			local geo = tSkinBoneCheckSelection[i]

			if A2UFn_IsBoneOrBiped geo then
				continue
			
			for m in geo.modifiers where ClassOf m == Skin do
			(
				local vertCount = skinOps.GetNumberVertices m
				local boneCount = skinOps.GetNumberBones m
				local unusedBones = #{1..boneCount}
				for v = 1 to vertCount do
				(
					local vertWeightBoneCount = skinOps.GetVertexWeightCount m v
					for b = 1 to vertWeightBoneCount do
					(
						local boneWeight = skinOps.GetVertexWeight m v b
						if boneWeight > 0 then
						(
							local boneID = skinOps.GetVertexWeightBoneID m v b
							unusedBones[boneID] = false
						)
					)
				)
				
				tBoneListUnusedBones[i] = unusedBones

				local unusedBoneNum = 0
				for j = boneCount to 1 by -1 where unusedBones[j] do
				(
					unusedBoneNum = unusedBoneNum + 1
					append tBoneListBoxItems[i] ("\n\t" + skinOps.GetBoneName m j 0)
				)
				
				tNodeListBoxItems[i] = "模型名：" + geo.name + "\t（" + (boneCount as string) + "根骨骼 / " + (unusedBoneNum as string) + "根无效骨骼）\n"
			)
		)
		
		CreateDialog Gujian4_SkinBoneCheckResult width:750 height:520 "蒙皮骨骼检查"
		Gujian4_SkinBoneCheckResult.InitData tNodeListBoxItems tBoneListBoxItems tBoneListUnusedBones tSkinBoneCheckSelection

		clearSelection()
		select tSkinBoneCheckSelection
	)

	fn __refreshChkRot = 
	(
		if chkNF_UseRoll.state or 
		chkNF_UsePitch.state or 
		chkNF_UseYaw.state then
			chkNF_Rot.state = true
		else
			chkNF_Rot.state = false
	)

	fn _CheckOverlapFrame skelRoot &overlapBoneCount = 
	(
		local haveAnyBoneOverlapped = false
		local nodes = A2UFn_GetAllSkeleton(skelRoot)
		for _node in nodes do
		(
			DeleteOverlapScaleKey _node &haveAnyBoneOverlapped &overlapBoneCount
		)
		return haveAnyBoneOverlapped
	)

	on btn_SingleCheckOverlapFrame pressed do
	(
		local overlapBoneCount = 0
		local bHaveOverlapBone = false

		local skelRoots = A2UFn_GetAllSkelRoot()
		for skelRoot in skelRoots do
		(
			_CheckOverlapFrame skelRoot &overlapBoneCount
		)

		if bHaveOverlapBone and overlapBoneCount > 0 then
		(
			messagebox("检查完成，清理了" + (overlapBoneCount as string) + "个有重叠缩放Scale关键点的节点!      ")
		)
		else
		(
			messagebox("检查完成，没有有重叠缩放Scale关键点的节点!      ")
		)
	)

	on btn_BatchCheckOverlapFrame pressed do
	(
		if not queryBox "确认 批量检查并修复 重叠缩放关键帧 ？       \n这么做会保存覆盖同文件夹的max文件！       \n       \n" then
			return ""
		
		local overlapFileCount = 0
		local overlapBoneCount = 0
		local outputLog = ""
		local initMaxFile = maxFileName

		setquietmode true
		for f in getfiles (maxFilePath + "\\*.max") do 
		(
			loadMaxFile f usefileunits:true
			local bHaveOverlapBone = _CheckOverlapFrame &overlapBoneCount
			if bHaveOverlapBone then
			(
				overlapFileCount = overlapFileCount + 1
				outputLog = outputLog + maxFileName + " \n"
			)
			saveMaxFile (maxFilePath + maxFileName)
		)

		loadMaxFile initMaxFile
		setquietmode false

		if overlapFileCount > 0 then
			messageBox ("批量重叠缩放关键帧检查并修复完成！      \n已修复以下文件有重叠缩放关键帧 (F11查看Log):      \n" + outputLog)
		else
			messageBox "批量重叠缩放关键帧检查完成！\n所有文件都没有重叠缩放关键帧！     "
	)

	on btn_CheckUnusedBone pressed do
	(
		CheckSelectBone()
	)

	on chkNF_Pos changed theState do
	(
		chkNF_UseX.state = theState
		chkNF_UseY.state = theState
		chkNF_UseZ.state = theState
	)
	on chkNF_UseX changed theState do
	(
		__refreshChkPos()
	)
	on chkNF_UseY changed theState do
	(
		__refreshChkPos()
	)
	on chkNF_UseZ changed theState do
	(
		__refreshChkPos()
	)
	
	on chkNF_Rot changed theState do
	(
		chkNF_UseRoll.state = theState
		chkNF_UsePitch.state = theState
		chkNF_UseYaw.state = theState
	)
	on chkNF_UseRoll changed theState do
	(
		__refreshChkRot()
	)
	on chkNF_UsePitch changed theState do
	(
		__refreshChkRot()
	)
	on chkNF_UseYaw changed theState do
	(
		__refreshChkRot()
	)

	on btnNF_NodeA pressed do
	(
		local arrSelect = getCurrentSelection()
		if arrSelect.count <= 0 then
		(
			messagebox("未选中任何物体！      ")
			return ""
		)
		if arrSelect.count > 1 then
		(
			messagebox("请勿选中多个物体！      ")
			return ""
		)
		edtNF_NodeA.text = arrSelect[1].name
	)

	on btnNF_NodeB pressed do
	(
		local arrSelect = getCurrentSelection()
		if arrSelect.count <= 0 then
		(
			messagebox("未选中任何物体！      ")
			return ""
		)
		if arrSelect.count > 1 then
		(
			messagebox("请勿选中多个物体！      ")
			return ""
		)
		edtNF_NodeB.text = arrSelect[1].name
	)
	
	on btnNF_Calc pressed do
	(
		if (not chkNF_UseX.checked) and (not chkNF_UseY.checked) and 
			(not chkNF_UseZ.checked) and (not chkNF_UseRoll.checked) and
			(not chkNF_UsePitch.checked) and (not chkNF_UseYaw.checked) then
		(
			messagebox("未启用任一轴向跟随!      ")
			return ""
		)

		if (lblNF_NodeA.text == "") or (lblNF_NodeB.text == "") then
		(
			messagebox("未指定物体!      ")
			return ""
		)

		local NodeA = getnodebyname edtNF_NodeA.text
		local NodeB = getnodebyname edtNF_NodeB.text
		-- print lblNF_NodeA.text
		-- print lblNF_NodeB.text

		if (NodeA == undefined) or (NodeB == undefined) then
		(
			messagebox("指定物体不存在!      ")
			return ""
		)

		holdMaxFile()
		local myAnimationStart = animationRange.start
		local myAnimationEnd = animationRange.end
		toolMode.coordsys #world

		local deltaPos = 0
		local deltaRot = 0
		at time myAnimationStart (
			deltaPos = NodeA.transform.pos - NodeB.transform.pos
			deltaRot = NodeA.transform.rotation - NodeB.transform.rotation
		)
		with animate on
		(
			for i = myAnimationStart to myAnimationEnd by 1 do
			(
				at time i (
					if (chkNF_UseRoll.checked) or (chkNF_UsePitch.checked) or (chkNF_UseYaw.checked) then
					(
						local NodeBRotEuler = quatToEuler (inverse NodeB.transform.rotation)
						local calcRotEuler = quatToEuler (inverse (NodeA.transform.rotation) - deltaRot)
						if (chkNF_UseRoll.checked) then
							NodeBRotEuler.x = calcRotEuler.x
						if (chkNF_UsePitch.checked) then
							NodeBRotEuler.y = calcRotEuler.y
						if (chkNF_UseYaw.checked) then
							NodeBRotEuler.z = calcRotEuler.z
						NodeB.rotation = eulerAngles NodeBRotEuler.x NodeBRotEuler.y NodeBRotEuler.z
					)
					
					if (chkNF_UseX.checked) then
						NodeB.pos.x = NodeA.transform.pos.x - deltaPos.x
					if (chkNF_UseY.checked) then
						NodeB.pos.y = NodeA.transform.pos.y - deltaPos.y
					if (chkNF_UseZ.checked) then
						NodeB.pos.z = NodeA.transform.pos.z - deltaPos.z
					)
			)
		)
	)

	-- 父子跟随运动 FP
	on btnFP_NodeA pressed do
	(
		local arrSelect = getCurrentSelection()
		if arrSelect.count <= 0 then
		(
			messagebox("未选中任何物体！      ")
			return ""
		)
		if arrSelect.count > 1 then
		(
			messagebox("请勿选中多个物体！      ")
			return ""
		)
		edtFP_NodeA.text = arrSelect[1].name
	)

	on btnFP_NodeB pressed do
	(
		local arrSelect = getCurrentSelection()
		if arrSelect.count <= 0 then
		(
			messagebox("未选中任何物体！      ")
			return ""
		)
		if arrSelect.count > 1 then
		(
			messagebox("请勿选中多个物体！      ")
			return ""
		)
		edtFP_NodeB.text = arrSelect[1].name
	)
	
	on btnFP_Calc pressed do
	(
		if (lblFP_NodeA.text == "") or (lblFP_NodeB.text == "") then
		(
			messagebox("未指定物体!      ")
			return ""
		)
		
		local NodeA = getnodebyname edtFP_NodeA.text
		local NodeB = getnodebyname edtFP_NodeB.text
		-- print lblNF_NodeA.text
		-- print lblNF_NodeB.text

		if (NodeA == undefined) or (NodeB == undefined) then
		(
			messagebox("指定物体不存在!      ")
			return ""
		)
			
		holdMaxFile()
		in coordsys world
		start = animationrange.start
		end = animationrange.end
		
		pointHelper = Point ()

		pointHelper.transform = NodeB.transform
		pointHelper.parent = NodeA
		
		with animate on
		(
			for i in start to end do
			(
				at time i
				NodeB.transform = pointHelper.transform
			)
		)
		
		delete pointHelper
	)
)

-- *origin位置和朝向会和bip01初始不同，这段偏差应该被保留
-- *origin只是将bip01的运动轨迹增量应用了上去
rollout Gujian4_RootOriginModifyUtil "Root/*origin/*skelid工具"
(
	button btnAddRootOriginSkelID "添加 Root/*origin/*skelid "
	checkbox chkAddSkelID "添加 *skelid 点" checked:false align:#center

	group "*origin 工具"
	(
		label lblCalcOrigin "——— 计算 *origin ———" align:#left offset:[0, 4]
		button btnCalcOriginFast "快速计算" across:2 align:#left tooltip:"如果max文件中只有一个*origin，点击即可快速计算它"
		button btnCalcOriginSelected "计算选中" align:#right tooltip:"如果max文件中有多个*origin，需要点击一个来计算它"
		
		checkbox chkKeepOffset "保持初始偏移" checked:true offset:[4, 8] tooltip:"开启时，*origin会保持和Bip01之间的初始 位移和角度"
		checkbox chkUseBF "Forward/Back (前后)" checked:false offset:[4, 2]
		checkbox chkUseLR "Right/Left (左右)" checked:false offset:[4, 0]
		checkbox chkUseT "Turning (转向)" checked:false offset:[4, 0]
		checkbox chkUseZ "Height (上下垂直)" align:#left checked:false offset:[4, 0]
		label lblZValue "垂直跟随距离:" align:#right across:2 offset:[14, 4]
		editText edtZValue "" align:#left width:50 offset:[20, 0]
		checkbox chkZZero "限制高度大于等于0" checked:false offset:[4, 0] tooltip:"开启时，*origin的Z轴保证大于等于0"
		
		label lblResetOrigin "——— 重置 *origin ———" align:#left offset:[0, 4]
		button btnResetOriginFast "快速重置" across:2 align:#left tooltip:"如果max文件中只有一个*origin，点击即可快速重置它"
		button btnResetOriginSelected "重置选中" align:#right tooltip:"如果max文件中有多个*origin，需要点击一个来重置它"
	)

	group "*skelid 工具"
	(
		button btnShowAllIDText "显示所有 *skelid"
		button btnHideAllIDText "隐藏所有 *skelid"
	)

	group "Ctrl_Bip01 点工具"
	(
		button btnAddCtrlBip "为选中的Bip01点 增加 Ctrl_Bip01 点"
	)

	fn CalcOrigin originBone bipBone = 
	(
		fileProperties.deleteProperty #custom "motionusebf"
		fileProperties.addProperty #custom "motionusebf" chkUseBF.checked
		fileProperties.deleteProperty #custom "motionuselr"
		fileProperties.addProperty #custom "motionuselr" chkUseLR.checked
		fileProperties.deleteProperty #custom "motionuset"
		fileProperties.addProperty #custom "motionuset" chkUseT.checked
		fileProperties.deleteProperty #custom "motionusez"
		fileProperties.addProperty #custom "motionuset" chkUseZ.checked
		
		local myAnimationRangeStart = animationRange.start
		local myAnimationRangeEnd = animationRange.end
		local deltaZ = 0

		local initialpos = [0,0,0]		--初始Root和Bip01的位置距离
		local initialrot = [0,0,0]		--初始Root和Bip01的位置距离

		if originBone == undefined then
		(
			messagebox("没有找到*origin点!      ")
			return ""
		)

		if bipBone == undefined then
		(
			messagebox("没有找到Bip01点!      ")
			return ""
		)

		if (not chkUseBF.checked) and (not chkUseLR.checked) and (not chkUseT.checked) and (not chkUseZ.checked) then
		(
			messagebox("未启用任一轴向的位移!      ")
			return ""
		)

		sliderTime = myAnimationRangeStart
		toolMode.coordsys #world

		-- set key for bipBone
		with animate on
		(
			at time myAnimationRangeStart
			(
				initialpos = bipBone.transform.pos
				initialrot = quatToEuler bipBone.transform.rotation
				deltaZ = bipBone.transform.pos.z - originBone.pos.z
			)

			for i = myAnimationRangeStart to myAnimationRangeEnd by 1 do
			(
				at time i 
				(
					-- Turning
					if chkUseT.checked then
					(
						if chkKeepOffset.checked then
							originBone.rotation = (eulerAngles 0 0 (initialrot.z - (quatToEuler bipBone.transform.rotation).z))
						else
							originBone.rotation = (eulerAngles 0 0 -(quatToEuler bipBone.transform.rotation).z)
					)
					else
					(
						originBone.rotation = (eulerAngles 0 0 0)
					)
					-- Left Right
					if chkUseLR.checked then
					(
						if chkKeepOffset.checked then
							originBone.pos.x = bipBone.transform.pos.x - initialpos.x
						else
							originBone.pos.x = bipBone.transform.pos.x
					)
					else
					(
						originBone.pos.x = 0
					)
					-- Back Forward
					if chkUseBF.checked then
					(
						if chkKeepOffset.checked then
							originBone.pos.y = bipBone.transform.pos.y - initialpos.y
						else
							originBone.pos.y = bipBone.transform.pos.y
					)
					else
					(
						originBone.pos.y = 0
					)
					-- Vertical
					if chkUseZ.checked then 
					(
						if (edtZValue.text.count != 0) then
							deltaZ = edtZValue.text as float
						if chkZZero.checked then
							originBone.pos.z = amax 0 (bipBone.transform.pos.z - deltaZ)
						else
							originBone.pos.z = bipBone.transform.pos.z - deltaZ
					)
					else
					(
						originBone.pos.z = 0
					)
				)
			)
		)
	)

	fn ResetOrigin originBone = 
	(
		fileProperties.deleteProperty #custom "motionusebf"
		fileProperties.deleteProperty #custom "motionuselr"
		fileProperties.deleteProperty #custom "motionuset"
		fileProperties.deleteProperty #custom "motionusez"
		chkUseBF.checked = false
		chkUseLR.checked = false
		chkUseT.checked = false
		chkUseZ.checked = false
	
		deleteKeys originBone #allKeys
		
		originBone.rotation = (eulerAngles 0 0 0)
		originBone.pos = [0,0,0]
	)

	on btnAddRootOriginSkelID pressed do
	(
		if chkAddSkelID.checked then
		(
			if not queryBox "确认为所有Bip01 添加 Root/*origin/*skelid 点？      " then
				return ""
		)
		else
		(
			if not queryBox "确认为所有Bip01 添加 Root/*origin 点？      " then
				return ""
		)

		local biped01Roots = A2UFn_GetAllBip01Root()
		if biped01Roots.count == 0 then
		(
			messagebox("没有找到任何Bip01物体      \n")
			return ""
		)

		-- 遍历质心
		for bipObj in biped01Roots do
		(
			-- Root 创建在 Bip01的位置（目前不需要）
			-- local pointPos = bipObj.transform.position
			-- pointPos = [pointPos.x, pointPos.y, 0]
			-- local pointRot = quatToEuler(bipObj.transform.rotation)
			-- pointRot = eulerAngles 0 0 ((pointRot.z + 90) * -1)
			
			-- 添加Root
			local skelRoot = bipObj.parent
			if skelRoot == undefined then
			(
				skelRoot = Dummy pos:[0,0,0] name:"Root" boxSize:[20,20,20]
				bipObj.parent = skelRoot
			)
			else if ClassOf(skelRoot) != Dummy then
			(
				delete skelRoot
				skelRoot = Dummy pos:[0,0,0] name:"Root" boxSize:[20,20,20]
				bipObj.parent = skelRoot
			)

			-- 添加*origin
			local originObj = A2UFn_GetOriginNodeBySkelRoot(skelRoot)
			if originObj == undefined then
			(
				originObj = Dummy pos:[0,0,0] name:"*origin" boxSize:[10,10,10]
				originObj.parent = skelRoot
			)
			else if not ClassOf(originObj) == Dummy then
			(
				delete originObj
				originObj = Dummy pos:[0,0,0] name:"*origin" boxSize:[10,10,10]
				originObj.parent = skelRoot
			)

			-- 添加*skelid
			if chkAddSkelID.checked then
			(
				local skelidObj = A2UFn_GetIDNodeBySkelRoot(skelRoot)
				if skelidObj == undefined then
				(
					local pos = skelRoot.position
					local rot = quatToEuler(skelRoot.rotation)
					skelidObj = Text name:"*skelid" text:"SKEL#ID" size:10 wirecolor:(color 200 200 200)
					setTransformLockFlags skelidObj #all
					skelidObj.parent = skelRoot
					skelidObj.rotation = (eulerAngles -90 rot.z 0)
					skelidObj.position = pos
				)
				else if not ClassOf(skelidObj) == Text then
				(
					delete skelidObj
					local pos = skelRoot.position
					local rot = quatToEuler(skelRoot.rotation)
					skelidObj = Text name:"*skelid" text:"SKEL#ID" size:10 wirecolor:(color 200 200 200)
					setTransformLockFlags skelidObj #all
					skelidObj.parent = skelRoot
					skelidObj.rotation = (eulerAngles -90 rot.z 0)
					skelidObj.position = pos
				)
			)
		)
		
		if chkAddSkelID.checked then
			messageBox("场景中目前有" + (biped01Roots.count as string) + "个Bip01物体\n添加了Root/*origin/*skelid点!      \n")
		else
			messageBox("场景中目前有" + (biped01Roots.count as string) + "个Bip01物体\n添加了Root/*origin点!      \n")
	)

	on btnCalcOriginFast pressed do
	(
		local originBone = undefined
		local bipBone = undefined
		
		local origins = A2UFn_GetAllOriginObjects()
		if origins.count < 1 then
		(
			messageBox("当前max文件中找不到*origin物体      \n")
			return false
		)
		else if origins.count > 1 then
		(
			messageBox("当前max文件中有超过一个*origin物体      \n请选中一个*origin物体，使用*origin计算的【计算选中】功能      \n")
			return false
		)
		else
		(
			originBone = origins[1]
			local skelRoot = originBone.parent
			bipBone = A2UFn_GetBip01RootBySkelRoot skelRoot
		)
		
		CalcOrigin originBone bipBone
		messageBox("*origin 计算完成!      ")
	)

	on btnCalcOriginSelected pressed do
	(
		local originBone = undefined
		local bipBone = undefined
		
		if selection.count == 1 and selection[1].name == "*origin" and ClassOf(selection[1]) == Dummy then
		(
			local _temp = selection[1]
			local _tempParent = _temp.parent
			if A2UFn_IsSkelRootObject(_tempParent) then
			(
				originBone = _temp
				bipBone = A2UFn_GetBip01RootBySkelRoot(_tempParent)
			)
			else
			(
				messageBox("当前选中的 *origin 物体\n父物体没有Bip01      ")
				return false
			)
		)
		else
		(
			messageBox("请选中 且 只选中一个 *origin 物体来计算      ")
			return false
		)

		CalcOrigin originBone bipBone
		messageBox("选中的 *origin 计算完成!      ")
	)

	on btnResetOriginFast pressed do
	(
		local originBone = undefined
		
		local origins = A2UFn_GetAllOriginObjects()
		if origins.count < 1 then
		(
			messageBox("当前max文件中找不到*origin物体      \n")
			return false
		)
		else if origins.count > 1 then
		(
			messageBox("当前max文件中有超过一个*origin物体      \n请选中一个*origin物体，使用*origin计算的【重置选中】功能      \n")
			return false
		)
		else
		(
			originBone = origins[1]
		)

		ResetOrigin originBone
		messageBox("*origin 重置完成!      ")
	)

	on btnResetOriginSelected pressed do
	(
		local originBone = undefined
		
		if selection.count == 1 and selection[1].name == "*origin" and ClassOf(selection[1]) == Dummy then
		(
			if A2UFn_IsOriginNodeObject(selection[1]) then
			(
				originBone = selection[1]
			)
			else
			(
				messageBox("当前选中的 *origin 物体\n父物体没有有效的Bip质心      ")
				return false
			)
		)
		else
		(
			messageBox("请选中 且 只选中一个 *origin 物体来重置      ")
			return false
		)

		ResetOrigin originBone
		messageBox("选中的 *origin 重置完成!      ")
	)
	
	on OriginModifyUtil open do
	(
		local pathid = fileProperties.findProperty #custom "motionusebf"
		if pathid != 0 then (
			chkUseBF.checked = fileProperties.getPropertyValue #custom pathid
		)

		pathid = fileProperties.findProperty #custom "motionuselr"
		if pathid != 0 then (
			chkUseLR.checked = fileProperties.getPropertyValue #custom pathid
		)

		pathid = fileProperties.findProperty #custom "motionuset"
		if pathid != 0 then (
			chkUseT.checked = fileProperties.getPropertyValue #custom pathid
		)

		pathid = fileProperties.findProperty #custom "motionusez"
		if pathid != 0 then (
			chkUseZ.checked = fileProperties.getPropertyValue #custom pathid
		)
	)

	on btnShowAllIDText pressed do
	(
		local IDObjects = A2UFn_GetAllIDObjects()
		for IDObj in IDObjects do
		(
			IDObj.isHidden = false
		)
		messageBox("已经显示所有*skelid物体      ")
	)

	on btnHideAllIDText pressed do
	(
		local IDObjects = A2UFn_GetAllIDObjects()
		for IDObj in IDObjects do
		(
			IDObj.isHidden = true
		)
		messageBox("已经隐藏所有*skelid物体      ")
	)
)

